package com.sinosoft.utils.finance;

import com.google.common.base.Preconditions;

import java.math.BigDecimal;

/**
 * @Auther: zouren
 * @Date: 2019/4/23 17:19
 * @Description:
 */
public class FinanceUtils {
    private final static int scale=8;
    private final static BigDecimal one = new BigDecimal("1");
    /**
     * 终值公式
     * 说明 @link(http://www.wps.cn/function/functionshow/type-et-FV.htm)
     *
     * @param rate    各期利率
     * @param nper    为总投资（或贷款）期，即该项投资（或贷款）的付款期总数。
     * @param pmt     为各期所应支付的金额，其数值在整个年金期间保持不变。如果忽略 pmt，则必须包含 pv 参数。
     * @param pv      现值，即从该项投资开始计算时已经入帐的款项，或一系列未来付款的当前值的累积和，也称为本金。如果省略 PV，则假设其值为零，并且必须包括 pmt 参数。
     * @param isFrist 以指定各期的付款时间是在期初还是期末，false为期末支付
     * @return 返回的对象使用setScale(2, BigDecimal.ROUND_HALF_UP);//保留两位小数
     */
    public static BigDecimal FV(double rate, int nper, double pmt, double pv, boolean isFrist) {
        Preconditions.checkArgument(nper>=0, "nper 必须大于0 ");
        Preconditions.checkArgument(rate>=0, "rate 必须大于0 ");
        BigDecimal pvBigDecimal = new BigDecimal(pv + "");
        BigDecimal pmtBigDecimal = new BigDecimal(pmt + "");
        BigDecimal fv = new BigDecimal("0");
        //如有现值时
        if (pvBigDecimal.compareTo(BigDecimal.ZERO) != 0) {
            fv = fv.subtract(FOrP(rate, nper, pv));
        }
        //如有年金时
        if (pmtBigDecimal.compareTo(BigDecimal.ZERO) != 0) {
            fv = fv.subtract(FA(rate, nper, pmt, isFrist));
        }
        return fv;
    }

    /**
     * 现值公式
     * 说明 @link(http://www.wps.cn/function/functionshow/type-et-PV.htm)
     *
     * @param rate    各期利率
     * @param nper    为总投资（或贷款）期，即该项投资（或贷款）的付款期总数。
     * @param pmt     为各期所应支付的金额，其数值在整个年金期间保持不变。如果忽略 pmt，则必须包含 pv 参数。
     * @param fv      为未来值，或在最后一次支付后希望得到的现金余额，如果省略 fv，则假设其值为零（一笔贷款的未来值即为零）
     * @param isFrist 以指定各期的付款时间是在期初还是期末，false为期末支付
     * @return 返回的对象使用setScale(2, BigDecimal.ROUND_HALF_UP);//保留两位小数
     */
    public static BigDecimal PV(double rate, int nper, double pmt, double fv, boolean isFrist) {
        Preconditions.checkArgument(nper>=0, "nper 必须大于0 ");
        Preconditions.checkArgument(rate>=0, "rate 必须大于0 ");
        BigDecimal fvBigDecimal = new BigDecimal(fv + "");
        BigDecimal pmtBigDecimal = new BigDecimal(pmt + "");
        BigDecimal pv = new BigDecimal("0");
        //如有终值时
        if (fvBigDecimal.compareTo(BigDecimal.ZERO) != 0) {
            pv = pv.subtract(FOrP(rate, -nper, fv));
        }
        //如有年金时
        if (pmtBigDecimal.compareTo(BigDecimal.ZERO) != 0) {
            pv = pv.subtract(PA(rate, nper, pmt, isFrist));
        }
        return pv;
    }

    /**
     *
     * 说明@link(http://www.wps.cn/function/functionshow/type-et-PMT.htm)
     * 基于固定利率及等额分期付款方式，返回贷款的每期付款额。
     * @param rate  各期利率
     * @param nper  为总投资（或贷款）期，即该项投资（或贷款）的付款期总数。
     * @param pv 现值，或一系列未来付款的当前值的累积和，也称为本金。
     * @param fv 终值，或在最后一次付款后希望得到的现金余额，如果省略 fv，则假设其值为零，也就是一笔贷款的未来值为零。
     * @param isFrist 用以指定各期的付款时间是在期初还是期末。，false为期末支付
     * @return 返回的对象使用setScale(2, BigDecimal.ROUND_HALF_UP);//保留两位小数
     */
    public static BigDecimal PMT(double rate, int nper, double pv , double fv, boolean isFrist){
        Preconditions.checkArgument(nper>=0, "nper 必须大于0 ");
        Preconditions.checkArgument(rate>=0, "rate 必须大于0 ");
        BigDecimal pmt=new BigDecimal("0");
        BigDecimal fvBigDecimal = new BigDecimal(fv + "");
        BigDecimal pvBigDecimal = new BigDecimal(pv + "");
        /**
         *如果年化收益率为零时
         */
//        if (rate == 0) {
//            return pmt.subtract(fvBigDecimal.divide(nperBigDecimal,scale,BigDecimal.ROUND_HALF_UP)).subtract(pvBigDecimal.divide(nperBigDecimal,scale,BigDecimal.ROUND_HALF_UP));
//        }
        //如有终值时
        if (fvBigDecimal.compareTo(BigDecimal.ZERO) != 0) {
            pmt = pmt.subtract(FA(rate, -nper, fv,isFrist));
        }
        //如有现值时
        if (pvBigDecimal.compareTo(BigDecimal.ZERO) != 0) {
            pmt = pmt.subtract(PA(rate, -nper, pv, isFrist));
        }
        return pmt;
    }

    /**
     * 年金转终值  终值转年金
     * 年金终值公式（F/A，i，n）=v*（（rate+1） 的nper次方-1）/rate  ，偿债基金公式（A/F，i，n）=v*  rate/（（rate+1） 的nper次方-1）
     *
     * @param rate
     * @param nper    为正是 年金终值公式  负时 偿债基金公式
     * @param v
     * @param isFrist
     * @return
     */
    private static BigDecimal FA(double rate, int nper, Double v, boolean isFrist) {
        BigDecimal rateBigDecimal = new BigDecimal(rate + "");
        BigDecimal vBigDecimal = new BigDecimal(v + "");
        //年金利率 （F/A，i，n）
        BigDecimal rateNum = rateBigDecimal.add(one).pow(Math.abs(nper)).subtract(one).divide(rateBigDecimal,scale,BigDecimal.ROUND_HALF_UP);
        if (nper <= 0) {
            //年金利率 （A/F，i，n）
            rateNum = one.divide(rateNum,scale,BigDecimal.ROUND_HALF_UP);
            if(isFrist){
                rateNum= rateNum.divide(rateBigDecimal.add(one),scale,BigDecimal.ROUND_HALF_UP);
            }
        }else if (isFrist) {
            //期初 多乘 (1+i)
            rateNum = rateNum.multiply(rateBigDecimal.add(one));

        }

        return vBigDecimal.multiply(rateNum);
    }

    /**
     * 年金现值公式(P/A,i,n)=v*（（rate+1） 的nper次方-1）/(inper*（rate+1） 的nper次方)),
     * 资金回收公式(A/P,i,n)=v*(inper*（rate+1） 的nper次方))/（（rate+1） 的nper次方-1）
     *
     * @param rate
     * @param nper    为正是 年金现值公式,  负时 资金回收公式
     * @param v
     * @param isFrist
     * @return
     */
    private static BigDecimal PA(double rate, int nper, Double v, boolean isFrist) {
        BigDecimal rateBigDecimal = new BigDecimal(rate + "");
        BigDecimal vBigDecimal = new BigDecimal(v + "");
        //（P/A，i，n）
        BigDecimal rateNum = rateBigDecimal.add(one).pow(Math.abs(nper)).subtract(one).divide(rateBigDecimal.multiply(rateBigDecimal.add(one).pow(Math.abs(nper))),scale,BigDecimal.ROUND_HALF_UP);
        if (nper <= 0) {
            //（A/P，i，n）
            rateNum = one.divide(rateNum,scale,BigDecimal.ROUND_HALF_UP);
            if(isFrist){
                rateNum= rateNum.divide(rateBigDecimal.add(one),scale,BigDecimal.ROUND_HALF_UP);
            }
        }else if(isFrist){
            //期初 多乘 (1+i)
            rateNum = rateNum.multiply(rateBigDecimal.add(one));
        }

        return vBigDecimal.multiply(rateNum);
    }


    /**
     * 现值转终值  终值转现值
     * rate的nper次方*v
     *
     * @param rate 为利率
     * @param nper 期数 当nper正时是 终值利率（F/P，i，n）为负是现值利率（p/F，i，n）
     * @param v    换算的值
     * @return
     */
    private static BigDecimal FOrP(double rate, int nper, Double v) {

        BigDecimal vBigDecimal = new BigDecimal(v + "");
        BigDecimal rateBigDecimal = new BigDecimal(rate + "");
        rateBigDecimal = rateBigDecimal.add(one);
        // 当nper正时是 终值利率（F/P，i，n）为负是现值利率（p/F，i，n）
        BigDecimal rateNum = rateBigDecimal.pow(Math.abs(nper));
        if (nper <= 0) {
            rateNum = one.divide(rateNum,scale,BigDecimal.ROUND_HALF_UP);
        }
        return vBigDecimal.multiply(rateNum);
    }


    public static void main(String[] args) {
        Preconditions.checkArgument(-1>=0, "nper 必须大于0 ");
    }


}
